import { API_PREFIX, API_KEY } from '@/config'
// import Toast from '@/app/components/base/toast'
import { useToast } from 'vue-toast-notification'
import type {
  AnnotationReply,
  MessageEnd,
  MessageReplace,
  ThoughtItem,
  VisionFile
} from '@/types/app'

const TIME_OUT = 100000

const ContentType = {
  json: 'application/json',
  stream: 'text/event-stream',
  form: 'application/x-www-form-urlencoded; charset=UTF-8',
  download: 'application/octet-stream' // for download
}

const baseOptions = {
  method: 'GET',
  mode: 'cors',
  credentials: 'include', // always send cookies、HTTP Basic authentication.
  headers: new Headers({
    'Content-Type': ContentType.json,
    Authorization: `Bearer ${API_KEY}` // 添加 API Key 认证
  }),
  redirect: 'follow'
}

export type WorkflowStartedResponse = {
  task_id: string
  workflow_run_id: string
  event: string
  data: {
    id: string
    workflow_id: string
    sequence_number: number
    created_at: number
  }
}

export type WorkflowFinishedResponse = {
  task_id: string
  workflow_run_id: string
  event: string
  data: {
    id: string
    workflow_id: string
    status: string
    outputs: any
    error: string
    elapsed_time: number
    total_tokens: number
    total_steps: number
    created_at: number
    finished_at: number
  }
}

export type NodeStartedResponse = {
  task_id: string
  workflow_run_id: string
  event: string
  data: {
    id: string
    node_id: string
    node_type: string
    index: number
    predecessor_node_id?: string
    inputs: any
    created_at: number
    extras?: any
  }
}

export type NodeFinishedResponse = {
  task_id: string
  workflow_run_id: string
  event: string
  data: {
    id: string
    node_id: string
    node_type: string
    index: number
    predecessor_node_id?: string
    inputs: any
    process_data: any
    outputs: any
    status: string
    error: string
    elapsed_time: number
    execution_metadata: {
      total_tokens: number
      total_price: number
      currency: string
    }
    created_at: number
  }
}

export type IOnDataMoreInfo = {
  conversationId?: string
  taskId?: string
  messageId: string
  errorMessage?: string
  errorCode?: string
}

export type IOnData = (message: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => void
export type IOnThought = (though: ThoughtItem) => void
export type IOnFile = (file: VisionFile) => void
export type IOnMessageEnd = (messageEnd: MessageEnd) => void
export type IOnMessageReplace = (messageReplace: MessageReplace) => void
export type IOnAnnotationReply = (messageReplace: AnnotationReply) => void
export type IOnCompleted = (hasError?: boolean) => void
export type IOnError = (msg: string, code?: string) => void
export type IOnWorkflowStarted = (workflowStarted: WorkflowStartedResponse) => void
export type IOnWorkflowFinished = (workflowFinished: WorkflowFinishedResponse) => void
export type IOnNodeStarted = (nodeStarted: NodeStartedResponse) => void
export type IOnNodeFinished = (nodeFinished: NodeFinishedResponse) => void

type IOtherOptions = {
  isPublicAPI?: boolean
  bodyStringify?: boolean
  needAllResponseContent?: boolean
  deleteContentType?: boolean
  onData?: IOnData // for stream
  onThought?: IOnThought
  onFile?: IOnFile
  onMessageEnd?: IOnMessageEnd
  onMessageReplace?: IOnMessageReplace
  onError?: IOnError
  onCompleted?: IOnCompleted // for stream
  getAbortController?: (abortController: AbortController) => void
  onWorkflowStarted?: IOnWorkflowStarted
  onWorkflowFinished?: IOnWorkflowFinished
  onNodeStarted?: IOnNodeStarted
  onNodeFinished?: IOnNodeFinished
}

function unicodeToChar(text: string) {
  return text.replace(/\\u[0-9a-f]{4}/g, (_match, p1) => {
    return String.fromCharCode(parseInt(p1, 16))
  })
}

const handleStream = (
  response: Response,
  onData: IOnData,
  onCompleted?: IOnCompleted,
  onThought?: IOnThought,
  onMessageEnd?: IOnMessageEnd,
  onMessageReplace?: IOnMessageReplace,
  onFile?: IOnFile,
  onWorkflowStarted?: IOnWorkflowStarted,
  onWorkflowFinished?: IOnWorkflowFinished,
  onNodeStarted?: IOnNodeStarted,
  onNodeFinished?: IOnNodeFinished
) => {
  if (!response.ok) throw new Error('Network response was not ok')

  const reader = response.body?.getReader()
  const decoder = new TextDecoder('utf-8')
  let buffer = ''
  let bufferObj: Record<string, any>
  let isFirstMessage = true

  function read() {
    let hasError = false
    reader
      ?.read()
      .then((result: any) => {
        if (result.done) {
          onCompleted && onCompleted()
          return
        }

        // 解码新的数据块
        const chunk = decoder.decode(result.value, { stream: true })
        buffer += chunk

        // 按行分割数据，保留最后一行（可能不完整）
        const lines = buffer.split('\n')
        buffer = lines.pop() || '' // 保留最后一行作为下次的缓冲区开始

        try {
          lines.forEach((line) => {
            const message = line.trim()
            if (message.startsWith('data: ')) {
              try {
                // 移除 "data: " 前缀并解析JSON
                const jsonStr = message.substring(6)
                if (jsonStr.trim() === '') return // 跳过空数据

                bufferObj = JSON.parse(jsonStr) as Record<string, any>

                // 检查错误状态
                if (bufferObj.status === 400 || !bufferObj.event) {
                  onData('', false, {
                    conversationId: bufferObj?.conversation_id,
                    messageId: bufferObj?.message_id || '',
                    errorMessage: bufferObj?.message,
                    errorCode: bufferObj?.code
                  })
                  hasError = true
                  onCompleted?.(true)
                  return
                }

                // 处理不同类型的事件
                switch (bufferObj.event) {
                  case 'message':
                  case 'agent_message': {
                    // 处理消息数据，确保Unicode字符正确转换
                    const messageContent = unicodeToChar(bufferObj.answer || '')
                    onData(messageContent, isFirstMessage, {
                      conversationId: bufferObj.conversation_id,
                      taskId: bufferObj.task_id,
                      messageId: bufferObj.id
                    })
                    isFirstMessage = false
                    break
                  }

                  case 'agent_thought':
                    onThought?.(bufferObj as ThoughtItem)
                    break

                  case 'message_file':
                    onFile?.(bufferObj as VisionFile)
                    break

                  case 'message_end':
                    onMessageEnd?.(bufferObj as MessageEnd)
                    break

                  case 'message_replace':
                    onMessageReplace?.(bufferObj as MessageReplace)
                    break

                  case 'workflow_started':
                    onWorkflowStarted?.(bufferObj as WorkflowStartedResponse)
                    break

                  case 'workflow_finished':
                    onWorkflowFinished?.(bufferObj as WorkflowFinishedResponse)
                    break

                  case 'node_started':
                    onNodeStarted?.(bufferObj as NodeStartedResponse)
                    break

                  case 'node_finished':
                    onNodeFinished?.(bufferObj as NodeFinishedResponse)
                    break

                  default:
                    console.log('未处理的事件类型:', bufferObj.event, bufferObj)
                    break
                }
              } catch (e) {
                // 如果JSON解析失败，可能是数据包被分割了
                console.warn('JSON解析失败，可能是数据包分割:', e, message)
                // 将这行重新加入缓冲区等待下次处理
                buffer = message + '\n' + buffer
              }
            }
          })
        } catch (e) {
          console.error('处理流式数据时发生错误:', e)
          onData('', false, {
            conversationId: undefined,
            messageId: '',
            errorMessage: `处理数据时发生错误: ${e}`
          })
          hasError = true
          onCompleted?.(true)
          return
        }

        if (!hasError) {
          read()
        }
      })
      .catch((error) => {
        console.error('读取流式数据时发生错误:', error)
        onCompleted?.(true)
      })
  }

  read()
}

const baseFetch = (url: string, fetchOptions: any, { needAllResponseContent }: IOtherOptions) => {
  const options = Object.assign({}, baseOptions, fetchOptions)

  const urlPrefix = API_PREFIX

  let urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`

  const { method, params, body } = options
  // handle query
  if (method === 'GET' && params) {
    const paramsArray: string[] = []
    Object.keys(params).forEach((key) =>
      paramsArray.push(`${key}=${encodeURIComponent(params[key])}`)
    )
    if (urlWithPrefix.search(/\?/) === -1) urlWithPrefix += `?${paramsArray.join('&')}`
    else urlWithPrefix += `&${paramsArray.join('&')}`

    delete options.params
  }

  if (body) options.body = JSON.stringify(body)

  // Handle timeout
  return Promise.race([
    new Promise((resolve, reject) => {
      setTimeout(() => {
        reject(new Error('request timeout'))
      }, TIME_OUT)
    }),
    new Promise((resolve, reject) => {
      globalThis
        .fetch(urlWithPrefix, options)
        .then((res: any) => {
          const resClone = res.clone()
          // Error handler
          if (!/^(2|3)\d{2}$/.test(res.status)) {
            try {
              const bodyJson = res.json()
              switch (res.status) {
                case 401: {
                  useToast().error('Invalid token')
                  return
                }
                default:
                  new Promise(() => {
                    bodyJson.then((data: any) => {
                      useToast().error(data.message)
                    })
                  })
              }
            } catch (e) {
              useToast().error(`${e}`)
            }

            return Promise.reject(resClone)
          }

          // handle delete api. Delete api not return content.
          if (res.status === 204) {
            resolve({ result: 'success' })
            return
          }

          // return data
          const data =
            options.headers.get('Content-type') === ContentType.download ? res.blob() : res.json()

          resolve(needAllResponseContent ? resClone : data)
        })
        .catch((err) => {
          useToast().error(err)
          reject(err)
        })
    })
  ])
}

export const upload = (fetchOptions: any): Promise<any> => {
  const urlPrefix = API_PREFIX
  const urlWithPrefix = `${urlPrefix}/file-upload`
  const defaultOptions = {
    method: 'POST',
    url: `${urlWithPrefix}`,
    data: {}
  }
  const options = {
    ...defaultOptions,
    ...fetchOptions
  }
  return new Promise((resolve, reject) => {
    const xhr = options.xhr
    xhr.open(options.method, options.url)
    for (const key in options.headers) xhr.setRequestHeader(key, options.headers[key])

    xhr.withCredentials = true
    xhr.onreadystatechange = function () {
      if (xhr.readyState === 4) {
        if (xhr.status === 200) resolve({ id: xhr.response })
        else reject(xhr)
      }
    }
    xhr.upload.onprogress = options.onprogress
    xhr.send(options.data)
  })
}

export const ssePost = (
  url: string,
  fetchOptions: any,
  {
    onData,
    onCompleted,
    onThought,
    onFile,
    onMessageEnd,
    onMessageReplace,
    onWorkflowStarted,
    onWorkflowFinished,
    onNodeStarted,
    onNodeFinished,
    onError
  }: IOtherOptions
) => {
  const options = Object.assign(
    {},
    baseOptions,
    {
      method: 'POST'
    },
    fetchOptions
  )

  const urlPrefix = API_PREFIX
  const urlWithPrefix = `${urlPrefix}${url.startsWith('/') ? url : `/${url}`}`

  const { body } = options
  if (body) options.body = JSON.stringify(body)

  globalThis
    .fetch(urlWithPrefix, options)
    .then((res: any) => {
      if (!/^(2|3)\d{2}$/.test(res.status)) {
        new Promise(() => {
          res.json().then((data: any) => {
            useToast().error(data.message || 'Server Error')
          })
        })
        onError?.('Server Error')
        return
      }
      return handleStream(
        res,
        (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => {
          if (moreInfo.errorMessage) {
            useToast().error(moreInfo.errorMessage)
            return
          }
          onData?.(str, isFirstMessage, moreInfo)
        },
        () => {
          onCompleted?.()
        },
        onThought,
        onMessageEnd,
        onMessageReplace,
        onFile,
        onWorkflowStarted,
        onWorkflowFinished,
        onNodeStarted,
        onNodeFinished
      )
    })
    .catch((e) => {
      useToast().error(e)
      onError?.(e)
    })
}

export const request = (url: string, options = {}, otherOptions?: IOtherOptions) => {
  return baseFetch(url, options, otherOptions || {})
}

export const get = (url: string, options = {}, otherOptions?: IOtherOptions) => {
  return request(url, Object.assign({}, options, { method: 'GET' }), otherOptions)
}

export const post = (url: string, options = {}, otherOptions?: IOtherOptions) => {
  return request(url, Object.assign({}, options, { method: 'POST' }), otherOptions)
}

export const put = (url: string, options = {}, otherOptions?: IOtherOptions) => {
  return request(url, Object.assign({}, options, { method: 'PUT' }), otherOptions)
}

export const del = (url: string, options = {}, otherOptions?: IOtherOptions) => {
  return request(url, { ...options, method: 'DELETE' }, otherOptions)
}
